/*

JS.DataGrid

	Properties:
		- schema
		- model
		- selectedRow
		- container
		- table
		- thead
		- tbody
		- subGrids
	
	Methods:
		- initialize( container, schema, model, buildHeader )
		- getRows()
		- refreshRow([rowIndex])
		- appendRow(dataItem)
		- insertRow(dateItem, rowIndex)
		- removeRow(rowIndex)
		- refreshRow(rowIndex)
		- editRow(rowIndex, [colIndex])
		- commitEdit()
		- cancelEdit()
		- TODO: refresh
		
	
	Events:
		- created
		- row_created
		- row_rollover
		- row_rollout
		- row_edit
		- row_edit_commit
		- row_edit_commit_failed
		- row_edit_commited
		- row_edit_cancel
		- row_edit_request
		- row_command
		- subgrid_created
		- footer_created
		- TODO: refresh
	
*/

// constructor
DataGrid = function() {
	this.inited = false;
	this.buildHeader = true;
	this.schema = null;
	this.model = null;
	this.selectedRow = null;
	this.container = null;
	this.table = null;
	this.thead = null;
	this.tbody = null;
	this.subGrids = new Array();
	this.collapsedColumns = new Array();
	// event listeners
	this.events = {
		grid_td_rollover: 	this.grid_td_rollover.bindAsEventListener(this),
		grid_td_rollout: 	this.grid_td_rollout.bindAsEventListener(this),
		grid_td_click: 		this.grid_td_click.bindAsEventListener(this),
		grid_input_keypress: 	this.grid_input_keypress.bindAsEventListener(this),
		grid_td_command: 	this.grid_td_command.bindAsEventListener(this),
		subgrid_created: 	this.subgrid_created.bindAsEventListener(this),
		subgrid_rowedit: 	this.subgrid_rowedit.bindAsEventListener(this),
		subgrid_rowedit_request:this.subgrid_rowedit_request.bindAsEventListener(this),
		document_mouseup: 	this.document_mouseup.bindAsEventListener(this),
		grid_select_focus:	this.grid_select_focus.bindAsEventListener(this),
		grid_select_blur:	this.grid_select_blur.bindAsEventListener(this)
	}
}
// extend from Event.Publisher 
Object.extend( DataGrid.prototype, Event.Publisher );

// validation enumeration
DataGrid.Validation = new Object();
DataGrid.Validation.REQUIRED 		= 1;
DataGrid.Validation.NUMERIC 		= 2;
DataGrid.Validation.PERCENTAGE 		= 4;
DataGrid.Validation.ALPHA 		= 8;
DataGrid.Validation.ALPHANUMERIC	= 16;
DataGrid.Validation.DATE		= 32;
DataGrid.Validation.EMAIL		= 64;
DataGrid.Validation.URL			= 128;

// initialize function, this one will create the table and parse schema & data
DataGrid.prototype.initialize = function(container, schema, model, buildHeader) {
	if(this.inited) return;
	// table
	var tbl = document.createElement("table");
	// add table to container
	container.appendChild(tbl);
	
	this.buildHeader = buildHeader;
	this.table = tbl;
	this.container = container;
	this.schema = schema;
	this.model = model;
	Element.addClassName(tbl, "grid");
	// schema
	for(var i=0; i<schema.length; i++) this.collapsedColumns[i] = false;
	// header
	if(buildHeader) this.createHeader(tbl, schema);
	// body
	if(this.tbody == null) {
		this.tbody = document.createElement("tbody");
		tbl.appendChild(this.tbody);
	}
	for(var i=0;i<model.length; i++) this.createRow(model[i], i);
	// footer
	this.refreshFooter();
	// hook to document events
	Event.observe(document, "mouseup", this.events.document_mouseup);
	this.inited = true;
	// dispatch 'created' event
	this.dispatchEvent("created");	
}

// returns a collection with the rows (TRs)
DataGrid.prototype.getRows = function() {
	return this.tbody.childNodes;
}

DataGrid.prototype.refresh = function() {
	this.clearContainerContents(this.container);
	this.table = null;
	this.thead = null;
	this.tbody = null;
	this.subGrids = new Array();
	this.inited = false;
	this.initialize(this.container, this.schema, this.model, this.buildHeader);
}

// appends a dataItem to the model and creates the row
DataGrid.prototype.appendRow = function(dataItem)  {
	var ix = this.model.length;
	this.insertRow(dataItem, ix);
	return ix;
}

// inserts a dataItem at the specified position to the model and creates the row
DataGrid.prototype.insertRow = function(dataItem, rowIx) {
	// add item
	if(rowIx >= this.model.length) {
		// last
		rowIx = this.model.length;
		this.model.push(dataItem);
	} else {
		// between
		this.model.splice(rowIx, 0, dataItem);
	}
	this.createRow(dataItem, rowIx);
	this.refreshFooter();
	return rowIx;
}

// removes a dataItem from the specified position from the model and removes the row
DataGrid.prototype.removeRow = function(rowIx) {
	var dataItem = this.model[rowIx];
	this.model.splice(rowIx, 1);
	var tr = this.tbody.childNodes[rowIx];
	if(this.selectedRow == tr) this.selectedRow = null;
	this.tbody.removeChild(tr);
	this.refreshFooter();
	return dataItem;
}

// refreshes a row and its contents
DataGrid.prototype.refreshRow = function(tr) {
	if(!isNaN(tr)) tr = this.tbody.childNodes[tr];
	this.clearContainerContents(tr);
	var dataItem = this.model[this.findRowIndex(tr)];
	this.createRowCells(tr, dataItem);
}

// edits a row and focus on the selected cell
DataGrid.prototype.editRow = function(tr, colIndex) {
	if(!isNaN(tr)) tr = this.tbody.childNodes[tr];
	if(this.selectedRow == tr) {
		// same row, focus on td
		var input = tr.childNodes[colIndex].childNodes[0];
		if(input && !input.hasFocus) {
			if(input.focus) input.focus();
			//if(input.select) input.select();
		}
		return;
	}
	// commit edit from any subgrids
	for(var i=0; i<this.subGrids.length; i++) {
		if(!this.subGrids[i].commitEdit()) return;
		this.subGrids[i].cancelEdit();
	}
	// commit edit for current grid
	if(!this.commitEdit()) return;
	this.cancelEdit();
	// set selected row
	this.selectedRow = tr;
	Element.removeClassName(tr, "over");
	Element.addClassName(tr, "selected");
	if(colIndex == undefined || colIndex == null) colIndex = 0;
	var rowIndex = this.findRowIndex(tr);
	var dataItem = this.model[rowIndex];
	for(var i=0; i<tr.childNodes.length; i++) {
		var td = tr.childNodes[i];
		var colSchema = this.schema[i];
		if(colSchema.editable) {
			// editable item
			this.clearContainerContents(td);
			td.cancelClick = false;
			var input = null;
			if(colSchema.type == "boolean") {
				// checkbox
				input = this.appendCellCheckBox(td, colSchema, dataItem[colSchema.propertyName]);
			} else if(colSchema.options) {
				// select
				input = this.appendCellSelectBox(td, colSchema, dataItem[colSchema.propertyName]);
			} else {
				// textbox
				input = this.appendCellEditbox(td, colSchema, dataItem[colSchema.propertyName]);
			}
			if(input != null && colIndex == i) {
				// focus
				//if(input.select) input.select();
				if(input.focus) input.focus();
			}
		}
	}
	// dispatch event
	this.dispatchEvent("row_edit", {rowIndex:rowIndex, dataItem:dataItem, schema:this.schema, colIndex:colIndex});
}

// commits the changes done to the current selected row
DataGrid.prototype.commitEdit = function() {
	if(this.selectedRow == null) return true;
	var tr = this.selectedRow;
	var rowIndex = this.findRowIndex(tr);
	var dataItem = this.model[rowIndex];
	
	var oldValues = new Object();
	var newValues = new Object();
	var hasChanges = false;
	var footerRebuild = false;
	for(var i=0; i<this.schema.length; i++) {
		var colSchema = this.schema[i]
		if(colSchema.editable) {
			// set new value
			var td = tr.childNodes[i];
			if(td.getValue) {
				var propName = colSchema.propertyName;
				var sValue = td.getValue();
				oldValues[propName] = dataItem[propName];
				newValues[propName] = this.convertValue(sValue, colSchema.type)
				if(oldValues[propName] != newValues[propName]) hasChanges = true;
				if(colSchema.calculateTotal) footerRebuild = true;
			}
		}
	}

	if(tr.validator && !tr.validator.validate()) {
		var e = {rowIndex:rowIndex, oldValues:oldValues, newValues:newValues, dataItem:dataItem, schema:this.schema};
		this.dispatchEvent("row_edit_commit_failed", e);
		return false;
	}
	if(hasChanges) {
		var e = {rowIndex:rowIndex, oldValues:oldValues, newValues:newValues, dataItem:dataItem, schema:this.schema, cancelCommit:false};
		this.dispatchEvent("row_edit_commit", e);
		if(!e.cancelCommit) {
			for(var propName in newValues) {
				if(typeof newValues[propName] != "function") dataItem[propName] = newValues[propName];
			}
			if(footerRebuild) this.refreshFooter();
			this.dispatchEvent("row_edit_commited", e);
		}
	}
	return true;
}

// removes the selected status from the row and converts back the editboxes to labels.
DataGrid.prototype.cancelEdit = function() {
	if(this.selectedRow == null) return
	var tr = this.selectedRow;
	this.selectedRow = null;
	if(tr.validator) {
		tr.validator.clear();
		tr.validator = null;
	}
	var rowIndex = this.findRowIndex(tr);
	var dataItem = this.model[rowIndex];
	for(var i=0; i<tr.childNodes.length; i++) {
		// apply values
		var td = tr.childNodes[i];
		var colSchema = this.schema[td.colIndex];
		if(colSchema.editable) {
			td.removeAttribute("getValue");
			td.cancelClick = false;
			this.clearContainerContents(td);
			this.appendCellLabel(td, colSchema, dataItem[colSchema.propertyName]);
		}
	}
	Element.removeClassName(tr, "selected");
	this.dispatchEvent("row_edit_cancel", {rowIndex:rowIndex, dataItem:dataItem, schema:this.schema});
}

// collapses columns
DataGrid.prototype.toogleColumns = function(colIndexes) {
	//var tr = this.selectedRow;
	//if(tr && tr.validator && !tr.validator.validate()) return;
	if(this.buildHeader) {
		for(var i=0; i<colIndexes.length; i++) {
			var colIx = colIndexes[i];
			var td = this.thead.firstChild.childNodes[colIx];
			if(!this.collapsedColumns[colIx]) {
				Element.addClassName(td, "cellOff");
			} else {
				Element.removeClassName(td, "cellOff");
			}
		}
	}
	for(var rowIx=0; rowIx<this.tbody.childNodes.length; rowIx++) {
		var tr = this.tbody.childNodes[rowIx];
		for(var i=0; i<colIndexes.length; i++) {
			var colIx = colIndexes[i];
			var td = tr.childNodes[colIx];
			if(!this.collapsedColumns[colIx]) {
				Element.addClassName(td, "cellOff");
			} else {
				Element.removeClassName(td, "cellOff");
			}
		}
	}
	if(this.tfoot) {
		var tr = this.tfoot.firstChild;
		for(var i=0; i<colIndexes.length; i++) {
			var colIx = colIndexes[i];
			var td = tr.childNodes[colIx];
			if(!this.collapsedColumns[colIx]) {
				Element.addClassName(td, "cellOff");
			} else {
				Element.removeClassName(td, "cellOff");
			}
		}
	}

	for(var i=0;i<colIndexes.length; i++) {
		this.collapsedColumns[colIndexes[i]] = !this.collapsedColumns[colIndexes[i]];
	}
}

/*
 * Group: Private methods
 */


// creates the grid's header
DataGrid.prototype.createHeader = function(tbl, schema) {
	// thead * tr
	this.thead = document.createElement("thead");
	var tr = document.createElement("tr");
	this.thead.appendChild(tr);
	for(var i=0; i<schema.length; i++) {
		// th
		var colSchema = schema[i];
		var th = document.createElement("th");
		if(colSchema.className) Element.addClassName(th, colSchema.className);
		var span = document.createElement("span");
		span.className = "label";
		span.innerHTML = colSchema.headerText;
		th.appendChild(span);
		tr.appendChild(th);	
	}
	tbl.appendChild(this.thead);
}

// creates the grid's footer
DataGrid.prototype.refreshFooter = function() {
	if(this.checkForFooter()) {
		if(this.tfoot != null) this.table.removeChild(this.tfoot);
		this.tfoot = document.createElement("tfoot");
		var tr = document.createElement("tr");
		this.tfoot.appendChild(tr);
		for(var i=0; i<this.schema.length; i++) {
			var colSchema = this.schema[i];
			var td = document.createElement("td");	
			if(colSchema.className) td.className = colSchema.className;
			tr.appendChild(td);
			var span = document.createElement("span");
			span.className = "label";
			if(colSchema.calculateTotal) {
				var total = this.calculateTotal(colSchema.propertyName);
				if(colSchema.formatString) {
					if(typeof(colSchema.formatString) == "function") {
						total = colSchema.formatString(total);
					} else {
						total = colSchema.formatString.replace("{v}", total);
					}
				} else if(colSchema.type == "number") {
					total = this.formatNumber(total);
				}
				span.appendChild( document.createTextNode(total) );	
			} else {
				span.innerHTML = "&nbsp;";
			}
			td.appendChild(span);
			if(this.collapsedColumns[i]) Element.addClassName(td, "cellOff");
		}
		this.table.appendChild(this.tfoot);
		this.dispatchEvent("footer_created", {tfoot:this.tfoot});
	}
}

DataGrid.prototype.checkForFooter = function() {
	for(var i=0;i<this.schema.length; i++) {
		if(this.schema[i].calculateTotal) return true;
	}
	return false;
}

DataGrid.prototype.calculateTotal = function(propName) {
	var t=0;
	for(var i=0; i<this.model.length; i++) t += this.model[i][propName];
	return t;
}

// creates a grid row
DataGrid.prototype.createRow = function(dataItem, rowIx) {
	var beforeTr = this.tbody.childNodes[rowIx];
	if(!beforeTr) beforeTr = null;
	// tr
	var tr = document.createElement("tr");
	// append cells
	this.createRowCells(tr, dataItem);
	// append row
	this.tbody.insertBefore(tr, beforeTr);
	// dispatch event
	this.dispatchEvent("row_created", {rowIndex:this.findRowIndex(tr), dataItem:dataItem, schema:this.schema});
}

// creates the row cells
DataGrid.prototype.createRowCells = function(tr, dataItem) {
	// read schema
	for(var i=0; i<this.schema.length; i++) {
		var colSchema = this.schema[i];
		var td = document.createElement("td");
		td.colIndex = i;
		switch(colSchema.type) {
			case("button"):
			case("linkbutton"):
				// cell button
				this.appendCellButton(td, colSchema, dataItem);
				break;
			case("schema"):
				// subgrid
				var subGrid = new DataGrid();
				this.subGrids.push(subGrid);
				// hook events
				Event.observe(subGrid, "created", this.events.subgrid_created);
				Event.observe(subGrid, "row_edit", this.events.subgrid_rowedit);
				Event.observe(subGrid, "row_edit_request", this.events.subgrid_rowedit_request);
				subGrid.initialize(td, colSchema.schema, dataItem[colSchema.propertyName], colSchema.buildHeader);
				break;
			default:
				// label
				this.appendCellLabel(td, colSchema, dataItem[colSchema.propertyName]);
				if(colSchema.editable) {
					// editable cell
					Event.observe(td, "click", this.events.grid_td_click);
					Element.addClassName(td, "editable");
				}
				break;
		}
		// hook events
		Event.observe(td, "mouseover", this.events.grid_td_rollover);
		Event.observe(td, "mouseout", this.events.grid_td_rollout);
		// custom css class
		if(colSchema.className) Element.addClassName(td, colSchema.className);
		if(this.collapsedColumns[i]) Element.addClassName(td, "cellOff");
		tr.appendChild(td);
	}
}

// appends value's label
DataGrid.prototype.appendCellLabel = function(td, schema, value) {
	if(value == undefined || value == null) value = "";
	var ctrl;
	td.cancelClick = false;
	switch(schema.type) {
		case("boolean"):
			ctrl = document.createElement("input");
			ctrl.type = "checkbox";
			ctrl.defaultChecked = value;
			ctrl.disabled = true;
			break;
		default:
			ctrl = document.createElement("span");
			ctrl.className = "label";
			if(schema.formatString) {
				if(typeof(schema.formatString) == "function") {
					value  = schema.formatString(value);
				} else {
					value  = schema.formatString.replace("{v}", value );
				}
			} else if(schema.type == "number") {
				value = this.formatNumber(value);
			}
			if (schema.options) {
			    for( var i=0;i<schema.options.length;i++) {
			        if (String(schema.options[i].value).toLowerCase() == String(value).toLowerCase()) {
			            value = schema.options[i].text;
			            break;
			        }
			    }			    
			}
			if(schema.textMode == "multiline") {
			    value = value.replace(/\n/ig, '<br />');
			    ctrl.innerHTML = value;
			} else {
			    ctrl.innerHTML = value;
			}
			break;
	}
	td.appendChild(ctrl);
	return ctrl;
}

// appends a command button
DataGrid.prototype.appendCellButton = function(td, schema, dataItem) {
	var ctrlBtn;
	td.cancelClick = true;
	switch(schema.type) {
		case("button"):
			ctrlBtn = document.createElement("input");
			ctrlBtn.type = "button"
			ctrlBtn.value = schema.text;
			break;
		case("linkbutton"):
			ctrlBtn = document.createElement("a");
			var span = document.createElement("span");
			span.appendChild(document.createTextNode(schema.text));
			ctrlBtn.appendChild(span);
			if(schema.hrefFunction != undefined && typeof(schema.hrefFunction) == "function") {
				ctrlBtn.href = schema.hrefFunction(dataItem);
			} else {
				ctrlBtn.href = "javascript:void(0)";					
			}
			
			break;
	}
	ctrlBtn.className = schema.className;
	if(schema.hrefFunction == undefined) Event.observe(ctrlBtn, "click", this.events.grid_td_command, true);
	td.appendChild(ctrlBtn);
	return ctrlBtn;
}

// appends value's editable box
DataGrid.prototype.appendCellEditbox = function(td, schema, value) {
	var tr = td.parentNode;
	td.cancelClick = false;
	var input;
	if(schema.textMode == "multiline") {
	    input = document.createElement("textarea");
	} else {
	    input = document.createElement("input");
	    input.type = "text";
	    input.setAttribute("autocomplete", "off");		// avoid mozilla bug
	}
	if (value) { 
	    input.value = value; 	    
	}
	input.className = "textbox";
	
	input.onblur = function() { this.clickCount = 0; };
	input.onclick = function() { this.clickCount++; };
	input.clickCount = 0;
	
	td.getValue = function() { return this.childNodes[0].value; }
	if(schema.textMode != "multiline") {
	    Event.observe(input, "keypress", this.events.grid_input_keypress);
	}
	// validation
	if(schema.validation) {
		// TODO: save validator reference in internal var ???
		if(!tr.validator || tr.validator == null) tr.validator = new FormValidation();
		/*
		DataGrid.Validation.REQUIRED 		= 1;
		DataGrid.Validation.NUMERIC 		= 2;
		DataGrid.Validation.PERCENTAGE 		= 4;
		DataGrid.Validation.ALPHA 		= 8;
		DataGrid.Validation.ALPHANUMERIC	= 16;
		DataGrid.Validation.DATE		= 32;
		DataGrid.Validation.EMAIL		= 64;
		DataGrid.Validation.URL			= 128;
		*/
		if((schema.validation & DataGrid.Validation.REQUIRED) > 0) tr.validator.addValidator( new FieldValidator(input, "required") );
		if((schema.validation & DataGrid.Validation.NUMERIC) > 0) tr.validator.addValidator( new FieldValidator(input, "numeric") );
		if((schema.validation & DataGrid.Validation.PERCENTAGE) > 0) tr.validator.addValidator( new FieldValidator(input, "percentage") );
		if((schema.validation & DataGrid.Validation.ALPHA) > 0) tr.validator.addValidator( new FieldValidator(input, "alpha") );
		if((schema.validation & DataGrid.Validation.ALPHANUMERIC) > 0) tr.validator.addValidator( new FieldValidator(input, "alphanumeric") );
		if((schema.validation & DataGrid.Validation.DATE) > 0) tr.validator.addValidator( new FieldValidator(input, "date") );
		if((schema.validation & DataGrid.Validation.EMAIL) > 0) tr.validator.addValidator( new FieldValidator(input, "email") );
		if((schema.validation & DataGrid.Validation.URL) > 0) tr.validator.addValidator( new FieldValidator(input, "url") );
		
	}
	td.appendChild(input);
	return input;
}

DataGrid.prototype.appendCellSelectBox = function(td, schema, value) {
	td.cancelClick = true;
	var select = document.createElement("select");
	select.style.width= "100%";
	for(var i=0; i<schema.options.length; i++) {
		var t = schema.options[i].text;
		var v = schema.options[i].value;
		if(v == undefined) v = t;
		select.options[select.options.length] = new Option(t,v);
	}
	this.setComboValue(select, value);
	
	Event.observe(select, "focus", this.events.grid_select_focus);
	Event.observe(select, "blur", this.events.grid_select_blur);
	
	td.appendChild(select);
	td.getValue = function() { var s=this.childNodes[0]; return s.options[s.selectedIndex].value; }
	return select;
}

// apeends values' checkbox
DataGrid.prototype.appendCellCheckBox = function(td, schema, value) {
	td.cancelClick = false;
	var chk = document.createElement("input");
	chk.type = "checkbox";
	chk.defaultChecked = value;
	td.appendChild(chk);
	td.getValue = function() { return this.childNodes[0].checked; }
	Event.observe(chk, "keypress", this.events.grid_input_keypress);
	return chk;
}

// clears cell's content
DataGrid.prototype.clearContainerContents = function(container) {
	while(container.childNodes.length > 0) container.removeChild(container.childNodes[0]);
}

DataGrid.prototype.convertValue = function(sValue, type) {
	var value = sValue
	// todo: boolean, date, etc
	switch(type) {
		case("number"):
			value = parseFloat(sValue);
			if(isNaN(value)) value = 0;
			break;
		case("boolean"):
			
	}
	return value;
}

DataGrid.prototype.findRowIndex = function(tr) {
	for(var i=0; i<this.tbody.childNodes.length; i++) {
		if(this.tbody.childNodes[i] == tr) return i;
	}
	return -1;
}

DataGrid.prototype.findColIndex = function(tr, td) {
	for(var i=0; i<tr.childNodes.length; i++) {
		if(tr.childNodes[i] == td) return i;
	}
	return -1;
}

DataGrid.prototype.findEditableCellIndex = function(tr, direction, colIndex) {
	// TODO: FIX THIS, NOT WORKING WITH COLLAPSED COLUMNS
	if(colIndex == undefined || colIndex == null) colIndex = -1;
	var newColIndex = colIndex + direction;
	var lastValidIndex = this.schema.length-1;
	if(direction == 1) {
		for(var i=newColIndex; i<this.schema.length; i++) {
			if(this.schema[i].editable && !Element.hasClassName(tr.childNodes[i], "cellOff")) {
				lastValidIndex = i;
				newColIndex = i;
				break;
			}
		}
	} else {
		for(var i=newColIndex; i>=0; i--) {
			if(this.schema[i].editable && !Element.hasClassName(tr.childNodes[i], "cellOff")) {
				newColIndex = i;
				break;
			}
		}
	}
	//alert(colIndex == newColIndex)
	//alert(lastValidIndex);
	if(newColIndex < 0 || newColIndex > lastValidIndex || !this.schema[newColIndex].editable) {
		if(direction == -1) {
			return this.findEditableCellIndex(tr, this.schema.length, -1);
		} else {
			return this.findEditableCellIndex(tr, 0, 1);
		}
	} else {
		return newColIndex;
	}
}

DataGrid.prototype.getComboValue = function(select) {
	var option = select.options[select.selectedIndex];
	if(option.value) {
		return option.value;
	} else {
		return option.label;
	}
}

DataGrid.prototype.setComboValue = function(select, value) {
	for(var i=0; i<select.options.length; i++) {
		if(String(select.options[i].value).toLowerCase() == String(value).toLowerCase()) {
			select.selectedIndex = i;
			return;
		}
	}
	for(var i=0; i<select.options.length; i++) {
		if(select.options[i].label == value) {
			select.selectedIndex = i;
			return;
		}
	}   		
}

/*
 * Group: Internal Event handlers
 */
 
// td.rollover
DataGrid.prototype.grid_td_rollover = function(e) {
	var tr = Event.findElement(e,"tr");
	var td = Event.findElement(e,"td");
	if(tr && tr != this.selectedRow) {
		if(!Element.hasClassName(tr, "selected")) Element.addClassName(tr, "over");
		var rowIndex = this.findRowIndex(tr)
		var dataItem = this.model[rowIndex];
		this.dispatchEvent("row_rollover", {dataItem:dataItem, rowIndex:rowIndex, colIndex:td.colIndex} );
	}
}
// td.rollout
DataGrid.prototype.grid_td_rollout = function(e) {
	var tr = Event.findElement(e,"tr");
	var td = Event.findElement(e,"td");
	if(tr && tr != this.selectedRow) {
		Element.removeClassName(tr, "over");
		var rowIndex = this.findRowIndex(tr)
		var dataItem = this.model[rowIndex];
		this.dispatchEvent("row_rollout", {dataItem:dataItem, rowIndex:rowIndex, colIndex:td.colIndex} );
	}
}
// td.click
DataGrid.prototype.grid_td_click = function(e) {
	var td = Event.findElement(e, "td");
	if(!td.cancelClick) this.editRow(td.parentNode, td.colIndex);
	//Event.stop(e);
}
// td.input.keypress
DataGrid.prototype.grid_input_keypress = function (e) {
	var tdSelected = Event.findElement(e, "td");
	var targetRow = null;
	var direction;
	var colIndex = tdSelected.colIndex;
	var endIt = false;
	switch(e.keyCode) {
		case(Event.KEY_RETURN):
			targetRow = (e.shiftKey) ? this.selectedRow.previousSibling : this.selectedRow.nextSibling;
			direction = (e.shiftKey) ? -1 : 1;
			endIt = e.ctrlKey;
			if(e.preventDefault) e.preventDefault();
			break;
		case(Event.KEY_UP):
			targetRow = this.selectedRow.previousSibling;
			direction = -1;
			if(e.preventDefault) e.preventDefault();
			break;
		case(Event.KEY_DOWN):
			targetRow = this.selectedRow.nextSibling;
			direction = 1;
			if(e.preventDefault) e.preventDefault();
			break;
		case(Event.KEY_ESC):
			this.cancelEdit();
			return true;
		case(Event.KEY_TAB):
			targetRow = this.selectedRow;
			colIndex = this.findEditableCellIndex(targetRow, (e.shiftKey)?-1:1, colIndex);
			if(e.preventDefault) e.preventDefault();
			break;
		default:
			return true;
	}
	
	
	if(targetRow) {
		if(targetRow != this.selectedRow) {
			if(!this.commitEdit()) return false;
		}
		if(endIt) {
			this.cancelEdit();
		} else {
			this.editRow(targetRow, colIndex);
		}
	} else {
		this.dispatchEvent("row_edit_request", {direction:direction,colIndex:colIndex});
		if(this.commitEdit()) this.cancelEdit();
	}
	
	return false;
}

DataGrid.prototype.grid_select_focus = function(e) { if(this.selectedRow) this.selectedRow.cancelDocumentClick = true; }
DataGrid.prototype.grid_select_blur = function(e) { if(this.selectedRow) this.selectedRow.cancelDocumentClick = false; }

// td.command (linkbutton / button)
DataGrid.prototype.grid_td_command = function(e) {
	var td = Event.findElement(e, "td");
	var tr = Event.findElement(e, "tr");
	var rowIndex = this.findRowIndex(tr)
	var dataItem = this.model[rowIndex];
	this.dispatchEvent("row_command", {rowIndex:rowIndex, dataItem:dataItem, commandName:this.schema[td.colIndex].commandName});
	Event.stop(e);
}

// sub-grid event handlers
// sub-grid on create
DataGrid.prototype.subgrid_created = function(e) {
	this.dispatchEvent("subgrid_created", {grid:e.event_data.event_target});
}

// sub-grid on row edit
DataGrid.prototype.subgrid_rowedit = function(e) {
	if(!this.commitEdit()) return;
	this.cancelEdit();
	for(var i=0; i<this.subGrids.length; i++) {
		if(this.subGrids[i] != e.event_data.event_target) {
			if(!this.subGrids[i].commitEdit()) return;
			this.subGrids[i].cancelEdit();
		}
	}
}

// sub-grid on edit request
DataGrid.prototype.subgrid_rowedit_request = function(e) {
	var targetGrid;
	var direction = e.event_data.data.direction;
	var colIndex = e.event_data.data.colIndex;
	for(var i=0; i<this.subGrids.length; i++) {
		if(e.event_data.event_target.table == this.subGrids[i].table) {
			targetGrid = this.subGrids[i+direction];
			if(targetGrid) {
				if(!e.event_data.event_target.commitEdit()) return;
				e.event_data.event_target.cancelEdit();
			}
		}
	}
	
	if(targetGrid) {
		var tr;
		var td;
		if(direction == 1) {
			// edit first row
			var tr = targetGrid.tbody.childNodes[0];
		} else {
			// edit last row
			var tr = targetGrid.tbody.childNodes[targetGrid.tbody.childNodes.length-1];
		}
		targetGrid.editRow(tr, colIndex);
	} else {
		if(e.event_data.event_target.commitEdit()) e.event_data.event_target.cancelEdit();
	}
}

// document event handlers
DataGrid.prototype.document_mouseup = function(e) {
	if(this.selectedRow != null && !this.selectedRow.cancelDocumentClick) {
		var x = Event.pointerX(e);
		var y = Event.pointerY(e);
		if(!Position.within(this.selectedRow, x, y)) {
			if(this.commitEdit()) this.cancelEdit();
		}
	}
}


// helpers functions
DataGrid.prototype.formatNumber = function(nStr)
{
    var minBegin = '', minEnd ='';
	nStr += '';
	x = nStr.split('.');
	x1 = x[0];
	if (x1 < 0)
	{
	    x1 = -x1;
	    minBegin = "<font color='red'>";
	    minEnd = "</font>";
	}
	x2 = x.length > 1 ? '.' + x[1] : '';
	var rgx = /(\d+)(\d{3})/;
	while (rgx.test(x1)) {
		x1 = x1.replace(rgx, '$1' + ',' + '$2');
	}
	return minBegin + x1 + x2 + minEnd;
}


/*
 * Group: Changelog
 *
 * 25/09/2006:
 *	- added support for boolean types
 *	- added command buttons (button, linkbutton types)
 *	- added more events (row_edit, row_edit_commit, row_edit_cancel, row_edit_request, row_command)
 *	- basic field validation using lib.validation.js
 *
 * 26/09/2006:
 *	- editRow now accepts an integer as first argument
 *	- replaced 'className' with prototype's Element.addClassName and Element.removeClassName
 *	- added 'row_created', 'subgrid_created' events.
 *	- replaced parseInt() with parseFloat() in DataGrid.convertValue()
 *	- added 'cancelClick' on <td>.
 *
 * 02/10/2006:
 *	- input.     only get called on the first cell's click
 *	- commit edit on last subgrid_rowedit_request
 *	- fixed error on 'grid_td_rollover'
 *	- fixed bug in td.cancelClick
 *
 * 04/10/2006:
 *	- refactored row creations
 *	- event 'row_edit_commit' now contains the property 'cancelCommit' for cancelling the edit changes
 *	- method insertRow()
 *	- method refreshRow()
 *	- method getRows()
 *	- method removeRow()
 *
 * 10/10/2006:
 *	- added 'percentage' validation to fields
 *	- wrapped event handlers
 *	- added 'row_edit_commited' event
 *	- control + enter now commits edit
 *
 * 19/10/2006:
 *	- some memory leaks fixed
 *
 * 20/10/2006:
 *	- collapsable columns
 *	- calculateTotals on footer
 *
 * 03/11/2006:
 *	- 'footer_created' event
 *	- calculated fields now get displayes using the formatString options from the column schema
 *	- support for comboboxes within grid schema
 * 
 * 14/11/2006:
 *	- schema 'formatString' now accepts a function
 *	- 'number' dataFields get formatted using formatNumber()
 *
 * 17/11/2006:
 *	- fixed some IE bugs
 *
 * 04/12/2006:
 *	- added 'row_edit_commit_failed' event
 *	- fixed focus issue when changing input with mouse click
 */